<script lang="ts" setup>
import { computed, onMounted, ref, unref, watch, nextTick, getCurrentInstance } from 'vue'
import axios from '@/request/http'
import tinymce, { Editor, RawEditorSettings } from 'tinymce'

import 'tinymce/themes/silver'
import 'tinymce/icons/default/icons'
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/anchor'
import 'tinymce/plugins/autolink'
import 'tinymce/plugins/autosave'
import 'tinymce/plugins/code'
import 'tinymce/plugins/codesample'
import 'tinymce/plugins/directionality'
import 'tinymce/plugins/fullscreen'
import 'tinymce/plugins/hr'
import 'tinymce/plugins/insertdatetime'
import 'tinymce/plugins/link'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/image'
import 'tinymce/plugins/toc'
import 'tinymce/plugins/nonbreaking'
import 'tinymce/plugins/noneditable'
import 'tinymce/plugins/pagebreak'
import 'tinymce/plugins/paste'
import 'tinymce/plugins/preview'
import 'tinymce/plugins/print'
import 'tinymce/plugins/save'
import 'tinymce/plugins/searchreplace'
import 'tinymce/plugins/spellchecker'
import 'tinymce/plugins/tabfocus'
import 'tinymce/plugins/table'
import 'tinymce/plugins/template'
import 'tinymce/plugins/textpattern'
import 'tinymce/plugins/visualblocks'
import 'tinymce/plugins/visualchars'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/textcolor'
import 'tinymce/plugins/colorpicker'
import 'tinymce/plugins/contextmenu'
import 'tinymce/plugins/media'
import 'tinymce/plugins/charmap'
import 'tinymce/plugins/help'
import '@npkg/tinymce-plugins/axupimgs'
import '@npkg/tinymce-plugins/bdmap'
import '@npkg/tinymce-plugins/indent2em'
import '@npkg/tinymce-plugins/importword'
import { v4 } from 'uuid'
import { getImgSrc } from '../tools'
import { useBindHandlers } from '../helper'
import { ElLoading, ElMessage } from 'element-plus'
// 绑定监听事件
const { bindHandlers } = useBindHandlers()
type Recordable<T = any> = Record<string, T>
interface Props {
  // 富文本对应文件编号
  wjbh: string
  // 富文本内容中附件文件编号集合
  wjbhs: string[]
  // 是否禁用
  disabled?: boolean
  // 宽度
  width?: string | number
  // 高度
  height?: string | number
  // 配置项
  options?: Partial<RawEditorSettings>
  // 菜单栏
  toolbar?: string | string[]
  // 插件
  plugins?: string | string[]
  // 上传富文本接口
  apiSczfc?: string
  // 下载富文本接口
  apiXzzfc?: string
  // 上传附件接口
  apiScfj?: string
  // 下载附件接口
  apiXzfj?: string
}
const props = withDefaults(defineProps<Props>(), {
  wjbh: '',
  wjbhs: () => [],
  disabled: false,
  width: () => 'auto',
  height: () => 400,
  options: () => ({}),
  toolbar: () => [
    'code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | styleselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat code codesample | table image media charmap emoticons hr pagebreak insertdatetime print preview | fullscreen | bdmap indent2em lineheight axupimgs importword searchreplace',
  ],
  plugins: () => [
    'print preview searchreplace directionality visualblocks visualchars fullscreen image link media template code codesample table charmap hr pagebreak nonbreaking tabfocus anchor insertdatetime advlist lists wordcount textpattern help bdmap indent2em axupimgs importword paste',
  ],
  apiSczfc: '/wjfw/sczfcls',
  apiXzzfc: '/wjfw/xzzfc',
  apiScfj: '/wjfw/scwjls',
  apiXzfj: '/wjfw/xzwj',
})
const emit = defineEmits(['change', 'update:value', 'update:wjbhs', 'update:wjbh'])
// 编辑器Id
const tinymceId = ref(`tiny-vue${v4()}`)
// 实例上绑定的属性
const { attrs } = getCurrentInstance()!
// 编辑器实例
const editorRef = ref<Editor>()
// 文本域标签
const elRef = ref<HTMLTextAreaElement>()
// 临时内容
const tempValue = ref('')
// 初始化配置
const initOptions = computed((): RawEditorSettings => {
  const publicPath = '/'
  const { height, options, toolbar, plugins } = props
  return {
    selector: `#${unref(tinymceId)}`,
    language_url: `${publicPath}tinymce/langs/zh_CN.js`,
    language: 'zh_CN',
    skin_url: `${publicPath}tinymce/skins/ui/oxide`,
    content_css: `${publicPath}tinymce/skins/ui/oxide/content.min.css`,
    images_upload_handler: handleImgUpload,
    file_picker_callback: fillPickerCall,
    images_file_types: 'jpeg,jpg,png,gif,bmp,webp', // 准许的图片格式
    convert_urls: false,
    branding: false, // 隐藏品牌，隐藏状态栏中显示的“ Powered by Tiny ”链接
    placeholder: '请输入内容', // 占位符
    toolbar,
    plugins,
    height,
    toolbar_mode: 'sliding',
    toolbar_sticky: true,
    paste_block_drop: false, // 禁用将内容拖放到编辑器中
    paste_data_images: true, // 粘贴data格式的图像 谷歌浏览器无法粘贴
    font_formats:
      '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif,Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats',
    paste_webkit_styles: 'none', // 允许在 WebKit 中粘贴时要保留的样式
    paste_tab_spaces: 2, // 将制表符转换成空格的个数
    content_style: `
          html, body                { height:100%;margin:0; }
          img                       { max-width:100%; height:auto; }
          a                         { text-decoration: none; }
          p                         { line-height:1.6; margin: 0px; }
          table                     { word-wrap:break-word; word-break:break-all;max-width:100%; border:none; border-color:#999; }
          .mce-object-iframe        { width:100%; box-sizing:border-box; margin:0; padding:0; }
          ul,ol                     { list-style-position:inside; }
          `,
    ...options,
    setup: (editor: Editor) => {
      editorRef.value = editor
      editor.on('init', (e) => initSetup(e))
    },
  }
})

onMounted(() => {
  nextTick(() => {
    tinymce.init(initOptions.value)
  })
})

// 图片上传自定义逻辑
const handleImgUpload = (
  blobInfo: any,
  success: (url: string) => void,
  failure: (text: string) => void,
) => {
  if (!props.apiScfj) {
    failure('请配置上传接口后使用图片插入')
    return false
  }
  const loading = ElLoading.service({ text: '上传中，请稍后...' })
  const formData = new FormData()
  formData.append('file', blobInfo.file || blobInfo.blob())
  axios
    .post(props.apiScfj, formData)
    .then((res: any) => {
      success(`../tinymce-editor${props.apiXzfj}?wjbh=${res.data.wjbh}`)
    })
    .catch((error) => {
      failure(`上传文件失败：${error}`)
    })
    .finally(() => {
      loading.close()
    })
}
// 粘贴剪切板内容
const fillPickerCall = (
  // eslint-disable-next-line @typescript-eslint/ban-types
  success: Function,
  _: unknown,
  meta: any,
) => {
  // 文件分类
  let filetype = '*'
  if (meta.filetype === 'image') {
    filetype = 'image/*'
  } else if (meta.filetype === 'media') {
    filetype = 'audio/*, video/*'
  }
  // 后端接收上传文件的地址
  // 模拟出一个input用于添加本地文件
  const input = document.createElement('input')
  input.setAttribute('type', 'file')
  input.setAttribute('accept', filetype)
  input.click()
  const api = props.apiScfj
  input.onchange = (file: any) => {
    if (!file.target.files || file.target.files.length === 0) {
      return
    }
    const fileObj = file.target.files[0]
    const loading = ElLoading.service({ text: '上传中，请稍后...' })
    const formData = new FormData()
    formData.append('file', fileObj, fileObj.name)
    axios
      .post(api, formData)
      .then((res: any) => {
        success(`../tinymce-editor${props.apiXzfj}?wjbh=${res.data.wjbh}`, {
          title: fileObj.name,
          text: fileObj.name,
        })
      })
      .catch((error) => {
        error.isError && ElMessage.error(`上传文件失败：${error.message}`)
      })
      .finally(() => {
        loading.close()
      })
  }
}

// 编辑器初始化
const initSetup = (e: any) => {
  const editor = unref(editorRef)
  if (!editor) {
    return
  }
  const value = tempValue.value || ''
  editor.setContent(value)
  bindModelHandlers(editor)
  bindHandlers(e, attrs, unref(editorRef))
}
// 设置文本框显示内容
const setValue = (editor: Recordable, val: string | undefined, prevVal?: string | undefined) => {
  if (editor && typeof val === 'string' && val !== prevVal && val !== editor.getContent()) {
    editor.setContent(val)
  }
}
// 通过文件编号获取富文本内容
const getValueUseWjbh = () => {
  props.wjbh &&
    axios
      .post(props.apiXzzfc, { wjbh: props.wjbh })
      .then((res: any) => {
        tempValue.value = res
        emit('update:value', res)
      })
      .catch((error) => {
        error.isError && ElMessage.error(`下载内容失败：${error.message}`)
      })
}
watch(
  () => props.wjbh,
  (val, oldVal) => {
    if (val && val !== oldVal) {
      getValueUseWjbh()
    }
  },
  { immediate: true },
)
// 绑定模板操作事件
const bindModelHandlers = (editor: Recordable) => {
  const modelEvents = attrs.modelEvents ? attrs.modelEvents : null
  const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents
  watch(
    tempValue,
    (val, prevVal) => {
      setValue(editor, val, prevVal)
      getFwbwjs()
    },
    { immediate: true },
  )
  watch(
    () => props.disabled,
    (val) => {
      editor.setMode(val ? 'readonly' : 'design')
    },
    { immediate: true },
  )
  editor.on(normalizedEvents || 'change keyup undo redo', () => {
    const content = editor.getContent()
    tempValue.value = content
    emit('update:value', content)
    emit('change', content)
  })
}
// 获取富文本中附件文件编号
const getFwbwjs = () => {
  emit('update:wjbhs', getImgSrc(tempValue.value || ''))
}
// 保存内容
const saveValue = async () => {
  const loding = ElLoading.service({ text: '上传中，请稍后...' })
  const res: any = await axios.post(props.apiSczfc, { content: tempValue.value }).catch((error) => {
    error.isError && ElMessage.error(`上传内容失败：${error.message}`)
    loding.close()
  })
  if (!res) {
    return
  }
  emit('update:wjbh', res.data.wjbh)
  loding.close()
}
// 外部调用方法
defineExpose({
  saveValue,
})
</script>
<template>
  <div class="prefix-cls">
    <textarea :id="tinymceId" ref="elRef" :style="{ visibility: 'hidden' }"></textarea>
  </div>
</template>
<style lang="scss" scoped>
.prefix-cls {
  position: relative;
  line-height: normal;
}
textarea {
  z-index: -1;
  visibility: hidden;
}
</style>
